-
Define how your program is organized in memory and in the object file.
-
They tell the assembler and linker where code and data go, and how they should be treated (readable, writable, executable, initialized, etc.).
-
The exact names and behavior depend on the format (ELF on Linux, PE on Windows, Mach-O on macOS), but the core concepts are consistent.
-
Linker may insert padding between sections:
align 16 -
At runtime:
-
Sections in your .asm file become sections in the ELF object file, which the linker groups into segments.
-
.text→ RX memory -
.data→ RW memory (preloaded) -
.bss→ RW memory (zeroed)
-
[ code ]
[ .rodata ]
[ .data ]
[ .bss ] ← fixed
-------------------------
[ heap ↑ ] ← grows up
-------------------------
[ stack ↓ ] ← grows down
_start
-
Is not just a convention. It is the default program entry symbol used by the linker (on ELF systems like Linux).
-
When you run a program:
-
Kernel loads the executable
-
Sets up stack (argc, argv, envp)
-
Jumps to the address of _start
-
-
No C runtime, no setup—just direct execution.
-
Is _start required?
-
By default: yes, linker expects it
-
But you can override:
ld -e my_entry file.o.
-
.text
-
The code section.
-
Contains executable instructions
-
Typically read + execute, not writable
-
Entry point (
_startormain) lives here -
Writing to
.text→ crash (NX protection)
.data
-
Initialized data.
-
Stores initialized global/static variables
-
Read + write
-
Values are embedded in the binary
.bss
-
Uninitialized data.
-
“reserve space, don’t store bytes”
-
Space is reserved but not initialized.
-
Read + write
-
Does not take space in the file.
-
Loader initializes it to zero at runtime
Why it exists
-
Efficiency.
-
Instead of:
big_array times 1000000 db 0 -
You write:
big_array resb 1000000 -
Result:
-
File size: small
-
Memory at runtime: same size
-
Loader zeroes it automatically
-
.rodata
-
It is not a container with capacity like an array.
-
It is a region of the program’s memory image whose size is defined by its contents.
-
Read-only data, prevents accidental modification
-
Constants, strings (in many toolchains)
section .rodata
pi dq 3.141592653589793
-
Writing to
.rodata→ crash (read-only) -
initialized at compile time
-
never modified at runtime
-
mapped read-only by OS
-
can be shared between processes
-
multiple programs using same library
-
OS maps same physical pages
-
This only works if memory is immutable.
-
-
Would it be ok to no use
.rodata:-
You can put everything in writable memory:
-
Program will still:
-
run correctly
-
produce the same results
-
-
Nothing requires
.rodata. -
No inherent speed penalty.
-
-
But you lose:
-
hardware-enforced immutability
-
-
.rodatais not about correctness—it’s about enforcing intent at the hardware level
-
Size
-
There is no fixed or intrinsic “capacity” for .rodata.
-
Its size is determined entirely by the program and the platform constraints.
-
"whatever size the compiler/linker emits, limited by the system"
-
Real limits:
-
32-bit systems
-
Typical virtual address space: ~2–4 GB total.
-
So
.rodatamust fit within that along with everything else. -
Large
.rodata(hundreds of MB) can become a problem.
-
-
64-bit systems
-
Typical virtual address space: terabytes+.
-
.rodatais effectively limited by:-
available RAM
-
OS limits
-
-
linker/toolchain constraints
-
Practically:
-
tens or hundreds of MB → fine
-
multi-GB → possible but unusual
-
-
-
-
Examples:
small tool → a few KB medium app → 100 KB – few MB large game/engine → 10–100+ MB (assets embedded) -
.rodatais mapped using virtual memory:-
backed by the executable file
-
loaded on demand (paging)
-
-
So:
-
it does not need to fully fit in RAM at once
-
pages are loaded as accessed
-
Addressing Constants
-
Making constants non-addressable is a deliberate design choice, not a necessity.
-
Why it can make sense to no address constants:
-
Constants don’t need storage, if a constant is purely a value:
-
42, true, 3.14 -
The compiler (1) can inline it, (2) encode it directly in instructions
-
Example:
-
mov eax, 42 ; no memory involved -
No address exists → so taking one is meaningless.
-
-
-
Avoids forcing memory allocation
-
If constants are addressable:
-
&CONST -
you must give them a location:
-
.rodataentry -
That means:
-
more memory usage
-
more symbols
-
less optimization freedom
-
-
-
Enables better optimization
-
Non-addressable constants allow:
-
constant folding
-
dead elimination
-
duplication without concern
-
-
Because:
-
no observable identity (no address)
-
-
-
Prevents identity confusion
-
If this is allowed:
-
&a == &b
-
-
what does it mean?
const a = 42 const b = 42 -
Should they:
-
have same address? (deduplicated)
-
have different addresses?
-
-
Non-addressable constants avoid this question entirely.
-
-
-
How languages deal with it:
-
value → no identity → no address
-
object → has identity → has address
-
Rust:
const X: i32 = 42; // no fixed address static X: i32 = 42; // has address-
const→ inline value -
static→ memory location
-
-
.stack
-
Defines stack region.
-
Usually handled by OS, not manually in simple programs.
-
A limited, pre-reserved region of virtual memory
-
See
Memory.md#Stack Memoryfor more information .
Avoiding using the stack
-
It’s entirely possible to write assembly without ever using the stack.
-
It means
-
No
push/pop -
No
call/ret(since they implicitly use the stack)-
Use
jmpinstead ofcall -
Manually manage return addresses in registers or fixed memory
-
-
No stack-based local variables
-
Use general-purpose registers (rax, rbx, etc.)
-
Or reserve global/static memory
-
-
No stack frames
-
-
It works well for:
-
Bootloaders
-
Embedded systems / bare metal
-
Tiny kernels or demos
-
Performance-critical inner loops
-
Situations with fixed control flow
-
-
Where it becomes painful
-
Deep function nesting
-
Recursion (basically impossible without a stack or equivalent)
-
Complex control flow
-
Interfacing with standard ABIs (they expect a stack)
-
-
Key limitations
-
No recursion unless you emulate a stack manually
-
Limited temporary storage (register pressure)
-
Harder to maintain and scale
-
Debugging is more manual
-